eletrotupi / tcc/ frontend/app/sleep/[id].tsxc1e0d981cc858cd5313dfca8a23c78875f423d9d
7.5 KB | 299 lines Raw
1
import { useState, useEffect, use } from "react";
2
import {
3
  StyleSheet,
4
  View,
5
  ScrollView,
6
  Pressable,
7
  ActivityIndicator,
8
} from "react-native";
9
import { Text } from "@/components/ui/Text";
10
import { useLocalSearchParams, router, Stack } from "expo-router";
11
import { format } from "date-fns";
12
import { ptBR } from "date-fns/locale";
13

14
import { Card, SubtleInfoCard } from "@/components/ui/Cards";
15
import { Badge } from "@/components/ui/Badge";
16
import { Button } from "@/components/ui/Button";
17
import { Col, Between, Row } from "@/components/ui/LayoutHelpers";
18
import { Section, SectionHeader } from "@/components/ui/Sections";
19
import { Theme, Colors, Spacing } from "@/constants/theme";
20
import { MaterialIcons } from "@expo/vector-icons";
21
import {
22
  useSleepRecord,
23
  useDeleteSleepRecord,
24
} from "@/hooks/useSleepRecord.queries";
25
import { formatDate, parseDateOnly } from "@/lib/utils/time";
26

27
export default function SleepDetailScreen() {
28
  const { id } = useLocalSearchParams<{ id: string }>();
29
  const { data: sleepRecord, isLoading, isError } = useSleepRecord(id);
30
  const deleteSleepRecord = useDeleteSleepRecord();
31
  const [minutesLeft, setMinutesLeft] = useState(0);
32
  const [canDelete, setCanDelete] = useState(false);
33

34
  useEffect(() => {
35
    const updateTimer = () => {
36
      const createdAt = sleepRecord?.createdAt;
37
      if (!createdAt) return;
38

39
      const msSinceCreation = Date.now() - new Date(createdAt).getTime();
40
      const deletionWindowMs = 5 * 60 * 1000;
41

42
      const newCanDelete = msSinceCreation < deletionWindowMs;
43
      const newMinutesLeft = Math.max(
44
        0,
45
        Math.ceil((deletionWindowMs - msSinceCreation) / 60000),
46
      );
47

48
      setCanDelete(newCanDelete);
49
      setMinutesLeft(newMinutesLeft);
50
    };
51

52
    if (isLoading) return;
53

54
    updateTimer(); // Set initial values
55

56
    const interval = setInterval(updateTimer, 1000);
57

58
    return () => clearInterval(interval);
59
  }, [sleepRecord, isLoading]);
60

61
  if (isLoading) {
62
    return (
63
      <View style={styles.centered}>
64
        <ActivityIndicator size="large" color={Colors.light.tint} />
65
      </View>
66
    );
67
  }
68

69
  if (isError || !sleepRecord) {
70
    return (
71
      <View style={styles.centered}>
72
        <Text style={styles.errorText}>Registro não encontrado.</Text>
73
        <Pressable onPress={() => router.back()} style={styles.backButton}>
74
          <Text style={styles.backButtonText}>Voltar</Text>
75
        </Pressable>
76
      </View>
77
    );
78
  }
79

80
  const handleDelete = async () => {
81
    await deleteSleepRecord.mutateAsync(String(sleepRecord.id));
82

83
    router.back();
84
  };
85

86
  const deleteButtonMsg = () => {
87
    const msg = "Excluir Registro";
88
    const timeLeftStr = `(Disp. por ${minutesLeft < 1 ? "<1" : minutesLeft}:00)`;
89
    const suffix = canDelete ? timeLeftStr : "(Expirado)";
90

91
    return `${msg} ${suffix}`;
92
  };
93

94
  return (
95
    <>
96
      <Stack.Screen options={{ title: "Ver detalhes" }} />
97

98
      <ScrollView
99
        style={styles.container}
100
        contentContainerStyle={styles.content}
101
        showsVerticalScrollIndicator={false}
102
      >
103
        {/* Header card */}
104
        <Card style={styles.headerCard}>
105
          <Row style={{ alignItems: "center" }}>
106
            <View style={[styles.iconWrap, { backgroundColor: '#ede9fe' }]}>
107
              <MaterialIcons name="bedtime" size={22} color="#7c3aed" />
108
            </View>
109
            <Col>
110
              <Row style={{ alignItems: "flex-end" }}>
111
                <Text style={styles.averageLabel}>{sleepRecord.average}h</Text>
112
                <Text style={styles.suffixLabel}>de sono</Text>
113
              </Row>
114

115
              <Text style={styles.dateLabel}>
116
                {formatDate(parseDateOnly(sleepRecord.date))}
117
              </Text>
118
            </Col>
119
          </Row>
120
        </Card>
121

122
        {/* Annotation / notes */}
123
        {sleepRecord.annotations && (
124
          <Section>
125
            <SectionHeader title="Anotações" variant="subtle" />
126
            <Card style={{ padding: Spacing.cardGap }}>
127
              <Text style={styles.annotation}>
128
                &quot;{sleepRecord.annotations?.trim()}&quot;
129
              </Text>
130
            </Card>
131
          </Section>
132
        )}
133

134
        {/* Delete */}
135
        <View style={styles.section}>
136
          <Text style={styles.sectionLabel}>GESTÃO DO REGISTRO</Text>
137
          <Button
138
            variant="outline"
139
            loading={false}
140
            disabled={!canDelete}
141
            onPress={() => router.push(`/sleep/edit/${sleepRecord.id}`)}
142
            title="Editar Registro"
143
          />
144

145
          <Button
146
            variant="danger"
147
            loading={deleteSleepRecord.isPending}
148
            disabled={!canDelete || deleteSleepRecord.isPending}
149
            onPress={handleDelete}
150
            title={deleteButtonMsg()}
151
          />
152

153
          <SubtleInfoCard
154
            style={styles.deleteInfo}
155
            text={`
156
              A exclusão de registros é permitida apenas nos primeiros 5 minutos
157
              após o envio para garantir a integridade do histórico emocional.
158
              `}
159
          />
160
        </View>
161
      </ScrollView>
162
    </>
163
  );
164
}
165

166
const styles = StyleSheet.create({
167
  container: {
168
    flex: 1,
169
    backgroundColor: Theme.colors.background,
170
  },
171
  content: {
172
    padding: Spacing.containerPadding,
173
    gap: Spacing.sectionGap,
174
    paddingBottom: 48,
175
  },
176
  iconWrap: {
177
    width: 40,
178
    height: 40,
179
    borderRadius: 20,
180
    alignItems: 'center',
181
    justifyContent: 'center',
182
    marginTop: 1
183
  },
184
  headerCardContent: {
185
    flexDirection: "row",
186
    alignItems: "center",
187
    gap: 8,
188
  },
189
  averageLabel: {
190
    height: '100%',
191
    fontSize: 24,
192
    fontWeight: "700",
193
    color: Colors.light.text,
194
  },
195
  suffixLabel: {
196
    height: '100%',
197
    fontSize: 17,
198
    fontWeight: "700",
199
    color: Colors.light.textSecondary,
200
  },
201
  dateLabel: {
202
    fontSize: 13,
203
    color: Colors.light.textSecondary,
204
  },
205
  centered: {
206
    flex: 1,
207
    justifyContent: "center",
208
    alignItems: "center",
209
    gap: 12,
210
  },
211
  errorText: {
212
    fontSize: 15,
213
    color: Colors.light.textSecondary,
214
  },
215
  backButton: {
216
    paddingHorizontal: 20,
217
    paddingVertical: 10,
218
    backgroundColor: Theme.colors.tint,
219
    borderRadius: Theme.borderRadius.md,
220
  },
221
  backButtonText: {
222
    fontWeight: "700",
223
    color: Theme.colors.text,
224
  },
225

226
  // Header
227
  headerCard: {
228
    padding: 16,
229
  },
230
  moodIcon: {
231
    fontSize: 40,
232
  },
233
  moodLabel: {
234
    fontSize: 17,
235
    fontWeight: "700",
236
    color: Colors.light.text,
237
  },
238
  moodTime: {
239
    fontSize: 13,
240
    color: Colors.light.textSecondary,
241
  },
242

243
  // Sections
244
  section: {
245
    gap: 8,
246
  },
247
  sectionLabel: {
248
    fontSize: 11,
249
    fontWeight: "600",
250
    letterSpacing: 0.8,
251
    color: Colors.light.textSecondary,
252
    textTransform: "uppercase",
253
  },
254

255
  // Components
256
  componentRow: {
257
    flexDirection: "row",
258
    justifyContent: "space-between",
259
    alignItems: "center",
260
    paddingVertical: 10,
261
  },
262
  componentLabel: {
263
    fontSize: 15,
264
    fontWeight: "700",
265
    color: Colors.light.text,
266
  },
267
  componentIntensity: {
268
    fontSize: 13,
269
    color: Colors.light.textSecondary,
270
  },
271
  componentIntensityGreen: {
272
    fontSize: 13,
273
    fontWeight: "700",
274
    color: Theme.colors.tint,
275
  },
276
  progressTrack: {
277
    height: 6,
278
    backgroundColor: Theme.colors.light.divider,
279
    borderRadius: Theme.borderRadius.full,
280
    marginBottom: 4,
281
    overflow: "hidden",
282
  },
283
  progressFill: {
284
    height: "100%",
285
    backgroundColor: Theme.colors.light.tint,
286
    borderRadius: Theme.borderRadius.full,
287
  },
288
  // Annotation
289
  annotation: {
290
    fontSize: 14,
291
    lineHeight: 22,
292
    color: Colors.light.textSecondary,
293
    fontStyle: "italic",
294
    padding: 4,
295
  },
296
  deleteInfo: {
297
    marginTop: 4,
298
  },
299
});